﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;
using AbstraX;
using AbstraX.Contracts;
using Microsoft.Practices.Unity;
using SolutionFramework.Controls;
using MvvmTreeView;
using System.Windows.Browser;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Automation;
using SolutionFramework.Entities;
using System.Windows.Threading;
using System.Threading;
using SolutionFramework.BuildServiceReference;
using System.Windows.Controls.Primitives;
using AbstraX.BuildWizard;
using Divelements.SandDock;
using SolutionFramework.Services;
using System.Windows.Media.Imaging;
using System.ServiceModel.DomainServices.Client;
using System.Diagnostics;
using UIObjects;
using DanielVaughan.Logging.Silverlight.UI;
using TreeBuilder.Contracts;
using AbstraX.ClientInterfaces;
using Utils;
using SLControlLibrary;
using Hydra.Shell.Services;
using Hydra.Shell.Interfaces;
using System.Xml;
using System.Runtime.Serialization;
using Hydra.Shell.Implementations;

namespace SolutionFramework.Views
{
    public partial class Designer : Page, ISynchronousProxy, IHydraUIInternals
    {
        private IHydraStatusBar statusBar;
        private ThreadSafeLog.Log log;
        private LogViewer logViewer;
        private TreeBuilderService treeBuilderService;
        private HydraClientService hydraShell;
        private HydraUIShell hydraUIShell;
        private SolutionFramework.App app;

        public Designer()
        {
            InitializeComponent();

            ThreadSafeLog.Log.Enabled = true;

            app = (SolutionFramework.App)App.Current;

            log = new ThreadSafeLog.Log(this);

            log.Info("Starting log on client");

            this.Unloaded += new RoutedEventHandler(Designer_Unloaded);
            app.UnhandledException += new EventHandler<ApplicationUnhandledExceptionEventArgs>(UnhandledException);

            ProviderClientManager.Container.RegisterType<IBuildProxy, BuildProxy>();
            ProviderClientManager.OnFatalError += new OnFatalErrorHandler(OnFatalError);
            ProviderClientManager.OnGeneralError += new OnGeneralErrorHandler(OnGeneralError);
            ProviderClientManager.OnInternalError += new OnInternalErrorHandler(OnInternalError);
            ProviderClientManager.OnInternalMessage += new OnInternalMessageHandler(OnInternalMessage);

            treeBuilderService = new TreeBuilderService();

            TreeBuilder.Loaded += (sender, e) =>
            {
                if (ModelsHierarchy.TreeView.SelectedNode != null)
                {
                    var parentBase = ModelsHierarchy.TreeView.SelectedNode.Tag as IParentBase;

                    if (parentBase != null)
                    {
                        treeBuilderService.RootSource = parentBase;

                        return;
                    }
                }

                treeBuilderService.RootSource = null;
            };

            IoC.RegisterInstance<ITreeBuilderService>(treeBuilderService);
            IoC.RegisterInstance<UIObjects.ILog>(log);

            CaptureOutput();
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            var context = new SolutionFrameworkDomainContext();
            var query = context.GetProviderListQuery();
            var container = ProviderClientManager.Container;
            var messageQueue = new Queue<string>();
            var waiting = false;

            statusBar = app.UIContainer.Resolve<IHydraStatusBar>();

            hydraShell = new HydraClientService(new PackageAgent(), this, this);
            hydraUIShell = new HydraUIShell(this, this.Workspace, hydraShell);

            OutputWindow.Log = log;
            logViewer = new LogViewer();

            logViewer.Visibility = System.Windows.Visibility.Collapsed;
            logViewer.OfflineMode = true;

            this.LayoutRoot.Children.Add(logViewer);

            ProviderClientManager.Initialize(log, true);

            var operation = context.Load<ProviderListItem>(query);

            operation.Completed += (sender2, e2) =>
            {
                ProviderClientManager.LoadProviders(operation.Entities);

                ProviderClientManager.OnRootsLoaded += (sender3, e3) =>
                {
                };
            };

            ProviderClientManager.StatusBar = statusBar;
            ProviderClientManager.SetStatus("Initializing...");

            var viewModel = container.Resolve<IModelViewModel>();

            viewModel.TreeView.Busy += new MvvmTreeView.BusyHandler(TreeView_Busy);
            viewModel.TreeView.OnAddEditNode += (ITreeNode parentNode, int imageIndex, string text, ref EditNode editNode) =>
            {
                editNode = ModelsHierarchy.TreeView.AddEditNode(parentNode, imageIndex, text);

                editNode.AfterLabelEdit += (sender2, e2) =>
                {
                    var editText = e2.Label;
                };
            };

            viewModel.TreeView.OnBeginEdit += node =>
            {
                ModelsHierarchy.TreeView.BeginEdit(node);
            };

            viewModel.TreeView.OnEndEdit += (node, cancel) =>
            {
                ModelsHierarchy.TreeView.EndEdit(node, cancel);
            };

            viewModel.OnLoadWizardWindow += (out IWizardContainerWindow windowOut, IWizardContainer wizardContainer) =>
            {
                var host = new WindowHost();
                var containerWindow = new BuildWizard(wizardContainer);
                var window = new Divelements.SandDock.Window("Build Wizard", containerWindow);

                window.InitialPosition = InitialWindowPosition.CenterCanvas;
                window.Height = containerWindow.Height + 50;
                window.Width = containerWindow.Width + 25;

                wizardContainer.OnDebugInfo += (sender2, debugInfo) =>
                {
                    txtInspectorResults.Text = debugInfo;
                };

                containerWindow.OnWizardAborted += (sender2, args) =>
                {
                    var frame = args.Value;

                    ProviderClientManager.AbortBuild(frame.URL);
                };

                containerWindow.Window = window;

                this.LayoutRoot.Children.Add(host);
                window.ShowModal(host);

                windowOut = containerWindow;
            };

            viewModel.NodeSelected += (n, b) =>
            {
                if (b == null)
                {
                    txtInspectorResults.Text = "";
                }
                else
                {
                    if (DocumentContainer.Items.OfType<WindowGroup>().Any(g => g.SelectedWindow.Title == "Tree Builder"))
                    {
                        var parentBase = b as IParentBase;

                        if (parentBase == null)
                        {
                            Debug.WriteLine("Resetting root source to null");
                        }
                        else
                        {
                            Debug.WriteLine("Setting root source to '" + parentBase.Name + "'");
                        }

                        treeBuilderService.RootSource = parentBase;

                        TreeBuilder.TreeView.ResetNodeMenu(TreeBuilder.TreeView.SelectedNode);
                    }

                    txtInspectorResults.Text = b.DebugInfo;
                }
            };

            viewModel.OnDebugInfo += (sender2, debugInfo) =>
            {
                txtInspectorResults.Text = debugInfo;
            };

            var treeView = ModelsHierarchy.TreeView.InternalTreeView;

            hydraShell.OnCorePackagesAvailable += (exceptions) =>
            {
                Debug.WriteLine(">>>>>>>>>>>>>>>>>>> Siting ModelTreeView");

                ((IHydraUIHierarchy)viewModel.TreeView).SetSite(hydraShell);

                var root = App.Current.RootVisual;

                root.GetVisualDescendantsAndSelf().OfType<IObjectWithSite>().Distinct().ToList().ForEach(o =>
                {
                    o.SetSite(hydraShell);
                });
            };

            viewModel.TreeView.InternalTreeView = treeView;

            treeView.SetVerticalScrollPercent(20);
            treeView.SetVerticalScrollAmount(ScrollAmountCombined.Large);

            var info = treeView.GetScrollInfo();

            ModelsHierarchy.DataContext = viewModel;
            ModelsHierarchy.TreeView.Focus();

            var timer = new DispatcherTimer();

            timer.Tick += (sender2, e2) =>
            {
                if (!waiting) // Monitor.TryEnter not working
                {
                    waiting = true;

                    try
                    {
                        var operation2 = context.GetStatusMessages();

                        operation2.Completed += (sender3, e3) =>
                        {
                            try
                            {
                                var messages = operation2.Value;

                                if (messages != null)
                                {
                                    messages.ToList().ForEach(m => messageQueue.Enqueue(m));
                                }
                            }
                            catch (Exception ex)
                            {
                                messageQueue.Enqueue(ex.Message);
                            }

                            waiting = false;
                        };
                    }
                    catch (Exception ex)
                    {
                        messageQueue.Enqueue(ex.Message);
                    }
                }

                if (messageQueue.Count > 0)
                {
                    ProviderClientManager.SetStatus(messageQueue.Dequeue());
                }
            };

            timer.Interval = new TimeSpan(0, 0, 0, 0, 1000); // one second
            timer.Start();

            var assembly = System.Windows.Application.Current.GetType().Assembly;
            var attributes = assembly.GetAttributes();

            HtmlPage.Document.SetProperty("title", attributes.Product);

            //if (Environment.CurrentDirectory == @"C:\Users\Ken\Desktop" || Environment.CurrentDirectory == @"C:\Users\u164225\Desktop")
            //{
            //    TestPackages();
            //}
        }

        private void TestPackages()
        {
            hydraShell.OnCorePackagesAvailable += (exceptions) =>
            {
                var serviceLoadOp = (ServiceLoadOperation)null;

                if (exceptions.Count > 0)
                {
                    Debugger.Break();
                }

                if (hydraShell.QueryService(new Guid("f31b1a97-8569-4de6-8287-9fd5bc1d50fd"), CommonInterfaces.IID_IUnknown, out serviceLoadOp))
                {
                    serviceLoadOp.ServiceLoaded += (sender2, e2) =>
                    {
                        if (hydraShell.QueryService(new Guid("f31b1a97-8569-4de6-8287-9fd5bc1d50fd"), CommonInterfaces.IID_IUnknown, out serviceLoadOp))
                        {
                            serviceLoadOp.ServiceLoaded += (sender3, e3) =>
                            {

                            };
                        }
                    };
                }

                if (hydraShell.QueryService(new Guid("9c8754c7-b721-4ff6-b49e-06eb8c765781"), CommonInterfaces.IID_IUnknown, out serviceLoadOp))
                {
                    serviceLoadOp.ServiceLoaded += (sender2, e2) =>
                    {

                    };
                }

                var packageGuid = new Guid("f47a510e-c273-44ce-b8d4-4437b2cb8bdf");

                if (hydraShell.IsPackageInstalled(packageGuid))
                {
                    var package = (IHydraPackage)null;

                    if (!hydraShell.IsPackageLoaded(packageGuid, out package))
                    {
                        var packageLoadOp = hydraShell.LoadPackage(packageGuid);

                        packageLoadOp.PackageLoaded += (sender, e) =>
                        {
                            package = e.Package;
                        };
                    }
                }
            };
        }

        private void ShowError(Exception ex)
        {
            if (Thread.CurrentThread.IsBackground)
            {
                var uiThread = new UIThread(this.Dispatcher);

                uiThread.Run(() =>
                {
                    ShowError(ex);
                });
            }
            else
            {
                var host = new WindowHost();
                var exceptionControl = new ExceptionControl
                {
                    Exception = ex
                };

                var caption = ex.GetType().Name + " was caught";
                var window = new Divelements.SandDock.Window(caption, exceptionControl);

                window.Icon = new BitmapImage(new Uri("/Images/Warning.png", UriKind.Relative));

                window.InitialPosition = InitialWindowPosition.CenterCanvas;
                window.Height = exceptionControl.Height + 50;
                window.Width = exceptionControl.Width + 25;

                ProviderClientManager.Log.Error(ex.Message, ex);

                exceptionControl.ViewLog += () =>
                {
                    ShowLog(logViewer);
                };

                exceptionControl.ShowInnerException += (e, e2) =>
                {
                    ShowError(e);
                };

                this.LayoutRoot.Children.Add(host);
                window.ShowModal(host);
            }
        }

        private void ShowLog(LogViewer logViewer)
        {
            var host = new WindowHost();

            if (this.LayoutRoot.Children.Contains(logViewer))
            {
                this.LayoutRoot.Children.Remove(logViewer);
                logViewer.Visibility = System.Windows.Visibility.Visible;
            }

            var caption = "Log Viewer";
            var window = new Divelements.SandDock.Window(caption, logViewer);

            window.Icon = new BitmapImage(new Uri("/Images/Warning.png", UriKind.Relative));

            window.InitialPosition = InitialWindowPosition.CenterCanvas;
            window.Height = logViewer.Height + 50;
            window.Width = logViewer.Width + 25;

            this.LayoutRoot.Children.Add(host);

            window.ShowModal(host);
        }

        private void ShowError(RemotableException ex)
        {
            if (Thread.CurrentThread.IsBackground)
            {
                var uiThread = new UIThread(this.Dispatcher);

                uiThread.Run(() =>
                {
                    ShowError(ex);
                });
            }
            else
            {
                var host = new WindowHost();
                var exceptionControl = new ExceptionControl
                {
                    RemoteException = ex
                };

                var caption = ex.ExceptionType + " was caught at the server";
                var window = new Divelements.SandDock.Window(caption, exceptionControl);

                window.Icon = new BitmapImage(new Uri("/Images/Warning.png", UriKind.Relative));

                window.InitialPosition = InitialWindowPosition.CenterCanvas;
                window.Height = exceptionControl.Height + 50;
                window.Width = exceptionControl.Width + 25;

                ProviderClientManager.Log.Error(ex.ToString());

                exceptionControl.ViewLog += () =>
                {
                    ShowLog(logViewer);
                };

                exceptionControl.ShowInnerException += (e, e2) =>
                {
                    ShowError(e2);
                };

                this.LayoutRoot.Children.Add(host);

                window.ShowModal(host);
            }
        }

        private void UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            ShowError(e.ExceptionObject);
            e.Handled = true;
        }

        private void OnInternalError(Exception exception)
        {
            Debug.WriteLine(exception.Message);
        }

        private void OnInternalMessage(string message)
        {
            Debug.WriteLine(message);
        }

        private void OnGeneralError(RemotableException exception)
        {
            OutputWindow.WriteLine(Colors.Red, exception.Message, true);
        }

        private void OnFatalError(RemotableException exception)
        {
            ShowError(exception);
        }

        private void Designer_Unloaded(object sender, RoutedEventArgs e)
        {
            ProviderClientManager.UnInitialize();
        }

        private void TreeView_Busy(ITreeNode treeNode, bool busy)
        {
            if (busy)
            {
                ProviderClientManager.SetStatus(treeNode.Text + " loading child nodes...");
            }
            else
            {
                ProviderClientManager.SetStatus("");
            }
        }

        private void CaptureOutput()
        {
            var totalGenerationStats = new TotalGenerationStats();
            var generatorStats = (GeneratorStats)null;
            var projectBuildStats = (ProjectBuildStats)null;

            OutputWindow.Log = ProviderClientManager.Log;

            // todo - should come from resource

            ProviderClientManager.OnSolutionOpened += (n) =>
            {
                var assembly = System.Windows.Application.Current.GetType().Assembly;
                var attributes = assembly.GetAttributes();

                HtmlPage.Document.SetProperty("title", n + " - " + attributes.Product);
            };

            ProviderClientManager.OnBuildRequested += (n) =>
            {
                generatorStats = new GeneratorStats();

                generatorStats.GeneratorName = n;
                totalGenerationStats.GeneratorStats.Add(generatorStats.GeneratorName, generatorStats);

                OutputWindow.WriteLine(Colors.Blue, "{0} requesting build", n);
                OutputWindow.Indent();
            };

            ProviderClientManager.OnBuildRequestCompleted += (n) =>
            {
                if (generatorStats.ProjectsFailed > 0)
                {
                    totalGenerationStats.GeneratorsFailed++;
                }
                else
                {
                    totalGenerationStats.GeneratorsSucceeded++;
                }

                OutputWindow.WriteLine("========== Build: {0} succeeded, {1} failed, {2} up-to-date, {3} skipped ==========", generatorStats.ProjectsSucceeded, generatorStats.ProjectsFailed, generatorStats.ProjectsUpToDate, generatorStats.ProjectsSkipped);
                OutputWindow.Unindent();
            };

            ProviderClientManager.OnBuildFinished += (a) =>
            {
                if (projectBuildStats.Errors > 0)
                {
                    generatorStats.ProjectsFailed++;
                }
                else
                {
                    // not correct (need up to date, skipped)

                    generatorStats.ProjectsSucceeded++;
                }

                OutputWindow.WriteLine(a.Message);
            };

            ProviderClientManager.OnBuildStarted += (a) =>
            {
                projectBuildStats = new ProjectBuildStats();
            };

            ProviderClientManager.OnCustomEventRaised += (a) =>
            {
                OutputWindow.WriteLine(a.Message);
            };

            ProviderClientManager.OnErrorRaised += (a) =>
            {
                projectBuildStats.Errors++;

                OutputWindow.WriteLine(Colors.Red, a.Message);
            };

            ProviderClientManager.OnMessageRaised += (a) =>
            {
                OutputWindow.WriteLine(a.Message);
            };

            ProviderClientManager.OnProjectFinished += (a) =>
            {
                OutputWindow.WriteLine(a.Message);
            };

            ProviderClientManager.OnProjectStarted += (a) =>
            {
                var projectName = (string)a.Properties.Where(d => ((string)d.Key) == "MSBuildProjectName").Single().Value;
                var platform = (string)a.Properties.Where(d => ((string)d.Key) == "Platform").Single().Value;
                var configuration = (string)a.Properties.Where(d => ((string)d.Key) == "Configuration").Single().Value;

                projectBuildStats.ProjectName = projectName;
                generatorStats.BuildStats.Add(projectBuildStats.ProjectName, projectBuildStats);

                OutputWindow.WriteLine("------ Build started: Project: {0}, Configuration: {1} {2} ------", projectName, configuration, platform);
            };

            ProviderClientManager.OnWarningRaised += (a) =>
            {
                OutputWindow.WriteLine(Colors.Orange, a.Message);
            };

            ProviderClientManager.OnOutputMessage += (c, m) =>
            {
                OutputWindow.WriteLine(c, m);
            };
        }

        public T Call<T>(string method, string parmsFormatString, params object[] parms)
        {
            var rootElement = string.Empty;
            var type = typeof(T);

            if (typeof(T).IsArray)
            {
                rootElement = "ArrayOf" + type.Name.RemoveEnd("[]");
            }
            else
            {
                rootElement = type.Name;
            }

            var dictionary = new XmlDictionary();
            var rootName = dictionary.Add(rootElement);
            var nameSpace = dictionary.Add("http://schemas.datacontract.org/2004/07/HydraShellServer.Implementations");
            var uri = new Uri(App.Current.Host.Source + "../../../HydraSynchronizationService.svc");
            var urlPrefix = string.Format("{0}/{1}?{2}", uri.AbsoluteUri, method, parmsFormatString);
            var parmsArray = parms.Select(s => HttpUtility.UrlEncode(s.ToString())).Cast<object>().ToArray();
            var url = string.Format(urlPrefix, parmsArray);
            var invokeResult = (string)HtmlPage.Window.Invoke("callSynchronizerService", url);
            var clientSerializer = new DataContractSerializer(typeof(T), rootName, nameSpace);
            
            return (T)clientSerializer.ReadObject(invokeResult.ToStream());
        }

        public Dictionary<Guid, IProtocolHandlerListItem> ProtocolHandlersList
        {
            get 
            {
                return hydraShell.ProtocolHandlersList;
            }
        }

        public UserControl MainPage
        {
            get
            {
                return this;
            }
        }

        public HandlerLoadOperation LoadProtocolHandler(Guid guidHandler)
        {
            return hydraShell.LoadProtocolHandler(guidHandler);
        }

        public ILog Log
        {
            get
            {
                return log;
            }

            set
            {
                throw new NotImplementedException();
            }
        }

        public Hydra.Shell.Interfaces.ItemGenerationOperation GenerateAbstraXItemFromID(string id)
        {
            var thisOperation = new Hydra.Shell.Interfaces.ItemGenerationOperation();
            var operation = ProviderClientManager.GenerateItemFromID<IBase>(id);

            operation.OnProgress += (sender, e) =>
            {
                thisOperation.SetProgress(e.PercentComplete);
            };

            operation.ExceptionOccured += (sender, e) =>
            {
                thisOperation.SetException(sender, e.Exception);
            };

            operation.Generated += (sender, e) =>
            {
                thisOperation.SetGenerated(sender, e.Item);
            };

            return thisOperation;
        }

        public IHydraStatusBar StatusBar
        {
            get 
            {
                return statusBar;
            }
        }

        HydraDocumentContainer IHydraUIInternals.DocumentContainer
        {
            get 
            {
                return this.DocumentContainer;
            }
        }

        public HydraDockSite DockSite
        {
            get 
            {
                return this.Workspace;
            }
        }
    }
}
